using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Mail;
using System.Web.Services;
using System.Collections;
using System.Web.Services.Description;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
using newtelligence.DasBlog.Runtime;
using newtelligence.DasBlog.Web.Core;
using newtelligence.DasBlog.Web.Services.Rsd;

namespace newtelligence.DasBlog.Web.Services
{
	/// <summary>
	/// Summary description for DasBlogEditting.
	/// </summary>
	public class EditServiceImplementation : WebService
	{
		public EditServiceImplementation()
		{
			InitializeComponent();
		}

		#region Component Designer generated code

		//Required by the Web Services Designer
		private IContainer components = null;

		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (disposing && components != null)
			{
				components.Dispose();
			}
			base.Dispose(disposing);
		}

		#endregion

		[WebMethod]
		public bool CanEdit(string username, string password)
		{
			if (!SiteConfig.GetSiteConfig().EnableEditService)
			{
				throw new ServiceDisabledException();
			}

			try
			{
				if (HttpContext.Current.Request.IsAuthenticated)
				{
					UserToken token = SiteSecurity.GetToken(User.Identity.Name);
					if (token.Role == "admin") return true;
				}

				return SiteSecurity.Login(username, password).Role == "admin";
			}
			catch (Exception e)
			{
				ErrorTrace.Trace(TraceLevel.Error, e);
				return false;
			}
		}

		[WebMethod]
		[SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Wrapped,Use=SoapBindingUse.Literal)]
		public RsdRoot GetRsd()
		{
            // TODO: NLS - Make the default API configurable through SiteConfig
			SiteConfig siteConfig = SiteConfig.GetSiteConfig();
			RsdApiCollection apiCollection = new RsdApiCollection();

			RsdRoot rsd = new RsdRoot();
			RsdService dasBlogService = new RsdService();
			dasBlogService.HomePageLink = Utils.GetBaseUrl(siteConfig);

			RsdApi metaWeblog    = new RsdApi();
			metaWeblog.Name      = "MetaWeblog";
			metaWeblog.Preferred = ( siteConfig.PreferredBloggingAPI == metaWeblog.Name );
			metaWeblog.ApiLink   = Utils.GetBloggerUrl(siteConfig);
			metaWeblog.BlogID    = dasBlogService.HomePageLink;
			apiCollection.Add(metaWeblog);

			RsdApi blogger    = new RsdApi();
			blogger.Name      = "Blogger";
			blogger.Preferred = ( siteConfig.PreferredBloggingAPI == blogger.Name );
			blogger.ApiLink   = Utils.GetBloggerUrl(siteConfig);
			blogger.BlogID    = dasBlogService.HomePageLink;
			apiCollection.Add(blogger);

            RsdApi moveableType    = new RsdApi();
            moveableType.Name      = "Moveable Type";
            moveableType.Preferred = ( siteConfig.PreferredBloggingAPI == moveableType.Name );
            moveableType.ApiLink   = Utils.GetBloggerUrl( siteConfig );
            moveableType.BlogID    = dasBlogService.HomePageLink;
            apiCollection.Add( moveableType );

			dasBlogService.RsdApiCollection = apiCollection;
			rsd.Services.Add(dasBlogService);

			return rsd;
		}

		[WebMethod]
		public void DeleteEntry(string entryId, string username, string password)
		{
			SiteConfig siteConfig = SiteConfig.GetSiteConfig();
			if (!siteConfig.EnableEditService)
			{
				throw new ServiceDisabledException();
			}

			if (SiteSecurity.Login(username, password).Role != "admin")
			{
				throw new Exception("Invalid Password");
			}

			ILoggingDataService logService = LoggingDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.LogDir));
			IBlogDataService dataService = BlogDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.ContentDir), logService);

			//dataService.DeleteEntry(entryId, siteConfig.CrosspostSites);
			DeleteEntry(entryId, siteConfig, logService, dataService);
		}

		[WebMethod]
		public string UpdateEntry(Entry entry, string username, string password)
		{
			SiteConfig siteConfig = SiteConfig.GetSiteConfig();
			if (!siteConfig.EnableEditService)
			{
				throw new ServiceDisabledException();
			}

			if (SiteSecurity.Login(username, password).Role != "admin")
			{
				throw new Exception("Invalid Password");
			}

			ILoggingDataService logService = LoggingDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.LogDir));
			IBlogDataService dataService = BlogDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.ContentDir), logService);

			EntrySaveState val = UpdateEntry(entry, null, null, siteConfig, logService, dataService);

            string rtn = string.Empty;
            if (val.Equals(EntrySaveState.Updated))
                rtn = entry.EntryId;
            else
                rtn = val.ToString();

            return rtn;
		}

		[WebMethod]
		public string CreateEntry(Entry entry, string username, string password)
		{
			SiteConfig siteConfig = SiteConfig.GetSiteConfig();
			if (!siteConfig.EnableEditService)
			{
				throw new ServiceDisabledException();
			}

			if (SiteSecurity.Login(username, password).Role != "admin")
			{
				throw new Exception("Invalid Password");
			}

			// ensure that the entryId was filled in
			//
			if (entry.EntryId == null || entry.EntryId.Length == 0)
			{
				entry.EntryId = Guid.NewGuid().ToString();
			}

			// ensure the dates were filled in, otherwise use NOW
			if (entry.CreatedUtc == DateTime.MinValue)
			{
				entry.CreatedUtc = DateTime.UtcNow;
			}
			if (entry.ModifiedUtc == DateTime.MinValue)
			{
				entry.ModifiedUtc = DateTime.UtcNow;
			}

			ILoggingDataService logService = LoggingDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.LogDir));
			IBlogDataService dataService = BlogDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.ContentDir), logService);

			SaveEntry(entry, string.Empty, null, siteConfig, logService, dataService);

			return entry.EntryId;
		}
        /// <summary>
        /// 12 Oct 2006 / MOT :
        /// Get entry by date and title.  If there is more than one match, the most recent entry will be returned.
        /// </summary>
        /// <param name="entryDate">Date the entry was posted.</param>
        /// <param name="entryTitle">Title of the post.</param>
        /// <returns>DasBlog.Runtime.Entry</returns>
        [WebMethod (MessageName="GetEntryByDateAndTitle",Description="Get entry by date and title.  If there is more than one match, the most recent entry will be returned.")]
        public Entry GetEntry(DateTime entryDate, string entryTitle)
        {
            SiteConfig siteConfig = SiteConfig.GetSiteConfig();
            ILoggingDataService logService = LoggingDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.LogDir));
            IBlogDataService dataService = BlogDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.ContentDir), logService);

            //Only pass in the date portion of the entry date, do not pass in the time portion.
            DayEntry dayEntry = dataService.GetDayEntry(entryDate.Date);

            //this replacement of characters in the title was lifted directly from
            //  DasBlog.Web.Core::TitleMapperModule.HandleBeginRequest()
            entryTitle = entryTitle.Replace(".aspx", "");
            entryTitle = entryTitle.Replace("+", "");
            entryTitle = entryTitle.Replace(" ", "");
            entryTitle = entryTitle.Replace("%2b", "");
            entryTitle = entryTitle.Replace("%20", "");

            //now that we have a properly formatted title, use it to get a specific
            //  entry from the DayEntry object.  If there is more than one match to the
            //  title, the most recent entry will be matched.
            Entry entry = dayEntry.GetEntryByTitle(entryTitle);

            return entry;
        }
        /// <summary>
        /// 12 Oct 2006 / MOT :
        /// Get entry by unique guid string.  This will return, at most, one entry.
        /// </summary>
        /// <param name="entryId">guid string</param>
        /// <returns>DasBlog.Runtime.Entry</returns>
        [WebMethod(MessageName = "GetEntryByEntryID", Description="Get entry by guid string.  There will only ever be at most one match.")]
        public Entry GetEntry(string entryId)
        {
            SiteConfig siteConfig = SiteConfig.GetSiteConfig();
            ILoggingDataService logService = LoggingDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.LogDir));
            IBlogDataService dataService = BlogDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.ContentDir), logService);

            Entry entry = dataService.GetEntry(entryId);
            return entry;
        }

		public static void DeleteEntry(string entryId, SiteConfig siteConfig, ILoggingDataService logService, IBlogDataService dataService)
		{
			try
			{
				Entry entry = dataService.GetEntry(entryId);
				string permalink = Utils.GetPermaLinkUrl(entry.EntryId);
				//string[] categories = entry.GetSplitCategories();

				dataService.DeleteEntry(entryId, siteConfig.CrosspostSites);

				BreakCache(siteConfig, entry.GetSplitCategories());

				// give the XSS upstreamer a hint that things have changed
				XSSUpstreamer.TriggerUpstreaming();

				// TODO: when we add support for more than just enclosures, we can't delete the entire folder
				DirectoryInfo enclosuresPath = new DirectoryInfo((Path.Combine(SiteConfig.GetBinariesPathFromCurrentContext(), entryId)));
				if (enclosuresPath.Exists) enclosuresPath.Delete(true);


				logService.AddEvent(
					new EventDataItem(
					EventCodes.EntryDeleted, entry.Title,
					permalink));
			}
			catch (Exception ex)
			{
				StackTrace st = new StackTrace();
				logService.AddEvent(
					new EventDataItem(EventCodes.Error, ex.ToString() + Environment.NewLine + st.ToString(), HttpContext.Current.Request.RawUrl));
			}
		}

		public static void SaveEntry(Entry entry, SiteConfig siteConfig, ILoggingDataService logService, IBlogDataService dataService)
		{
			SaveEntry( entry, string.Empty, null, siteConfig, logService, dataService );
		}

		public static void SaveEntry(Entry entry, CrosspostInfoCollection crosspostList, SiteConfig siteConfig, ILoggingDataService logService, IBlogDataService dataService)
		{
			SaveEntry(entry, string.Empty, crosspostList, siteConfig, logService, dataService);
		}

        public static void SaveEntry(Entry entry, TrackbackInfoCollection trackbackList, SiteConfig siteConfig, ILoggingDataService logService, IBlogDataService dataService)
        {
            InternalSaveEntry(entry, trackbackList, null, siteConfig, logService, dataService);

            logService.AddEvent(
                new EventDataItem(
                EventCodes.EntryAdded, entry.Title,
                Utils.GetPermaLinkUrl( siteConfig, entry.EntryId)));
        }

        public static void SaveEntry( Entry entry, string trackbackUrl, CrosspostInfoCollection crosspostList, SiteConfig siteConfig, ILoggingDataService logService, IBlogDataService dataService )
        {
            TrackbackInfoCollection trackbackList = null;
            if ( trackbackUrl != null && trackbackUrl.Length > 0 )
            {
                trackbackList = new TrackbackInfoCollection();
                trackbackList.Add( new TrackbackInfo(
                    trackbackUrl,
                    Utils.GetPermaLinkUrl( siteConfig, entry ),
                    entry.Title,
                    entry.Description,
                    siteConfig.Title ) );
            }
            InternalSaveEntry( entry, trackbackList, crosspostList, siteConfig, logService, dataService );

            logService.AddEvent(
                new EventDataItem(
                EventCodes.EntryAdded, entry.Title,
                Utils.GetPermaLinkUrl( siteConfig, entry.EntryId ) ) );
        }

		public static void SaveEntry(Entry entry, TrackbackInfoCollection trackbackList, CrosspostInfoCollection crosspostList, SiteConfig siteConfig, ILoggingDataService logService, IBlogDataService dataService)
		{
			InternalSaveEntry(entry, trackbackList, crosspostList, siteConfig, logService, dataService);

			logService.AddEvent(
				new EventDataItem(
					EventCodes.EntryAdded, entry.Title,
					Utils.GetPermaLinkUrl( siteConfig, entry.EntryId)));
		}

		public static EntrySaveState UpdateEntry(Entry entry, string trackbackUrl, CrosspostInfoCollection crosspostList, SiteConfig siteConfig, ILoggingDataService logService, IBlogDataService dataService)
		{
            EntrySaveState rtn = EntrySaveState.Failed;

            entry.ModifiedLocalTime = DateTime.Now;

            TrackbackInfoCollection trackbackList = null;
            if ( trackbackUrl != null && trackbackUrl.Length > 0 )
            {
                trackbackList = new TrackbackInfoCollection();
                trackbackList.Add( new TrackbackInfo(
                    trackbackUrl,
                    Utils.GetPermaLinkUrl( siteConfig, entry ),
                    entry.Title,
                    entry.Description,
                    siteConfig.Title ) );
            }

			rtn = InternalSaveEntry(entry, trackbackList, crosspostList, siteConfig, logService, dataService);

            logService.AddEvent(
				new EventDataItem(
					EventCodes.EntryChanged, entry.Title,
					Utils.GetPermaLinkUrl(entry.EntryId)));

            return rtn;
		}

        private static EntrySaveState InternalSaveEntry(Entry entry, TrackbackInfoCollection trackbackList, CrosspostInfoCollection crosspostList, SiteConfig siteConfig, ILoggingDataService logService, IBlogDataService dataService)
        {

            EntrySaveState rtn = EntrySaveState.Failed;
			// we want to prepopulate the cross post collection with the crosspost footer
			if (siteConfig.EnableCrossPostFooter && siteConfig.CrossPostFooter != null && siteConfig.CrossPostFooter.Length > 0)
			{
				foreach (CrosspostInfo info in crosspostList)
				{
					info.CrossPostFooter = siteConfig.CrossPostFooter;
				}
			}

			// now save the entry, passign in all the necessary Trackback and Pingback info.
			try
			{
				// if the post is missing a title don't publish it
				if (entry.Title == null || entry.Title.Length == 0)
				{
					entry.IsPublic = false;
				}

                // if the post is missing categories, then set the categories to empty string.
                if (entry.Categories == null)
                    entry.Categories = "";

				rtn = dataService.SaveEntry(
					entry,
					(siteConfig.PingServices.Count > 0) ?
						new WeblogUpdatePingInfo(siteConfig.Title, Utils.GetBaseUrl(siteConfig), Utils.GetBaseUrl(siteConfig), Utils.GetRssUrl(siteConfig), siteConfig.PingServices) : null,
					(entry.IsPublic) ?
						trackbackList : null,
					siteConfig.EnableAutoPingback && entry.IsPublic ?
						new PingbackInfo(
							Utils.GetPermaLinkUrl( siteConfig, entry ),
							entry.Title,
							entry.Description,
							siteConfig.Title) : null,
					crosspostList);

				SendEmail(entry, siteConfig, logService);

			}
			catch (Exception ex)
			{
				StackTrace st = new StackTrace();
				logService.AddEvent(
					new EventDataItem(EventCodes.Error, ex.ToString() + Environment.NewLine + st.ToString(), ""));
			}

			// we want to invalidate all the caches so users get the new post
			BreakCache(siteConfig, entry.GetSplitCategories());

			// give the XSS upstreamer a hint that things have changed
			XSSUpstreamer.TriggerUpstreaming();

            return rtn;
		}

		private static void BreakCache(SiteConfig siteConfig, string[] categories)
		{
			// break the caching
			HttpRuntime.Cache.Remove("BlogCoreData");
			HttpRuntime.Cache.Remove("Rss::" + siteConfig.RssDayCount.ToString() + ":" + siteConfig.RssEntryCount.ToString());

			foreach(string category in categories)
			{
				string CacheKey = "Rss:" + category + ":" + siteConfig.RssDayCount.ToString() + ":" + siteConfig.RssEntryCount.ToString();
				HttpRuntime.Cache.Remove(CacheKey);
			}
		}

		/// <summary>
		/// Send an email notification that an entry has been made.
		/// </summary>
		/// <param name="siteConfig">The page making the request.</param>
		/// <param name="entry">The entry being added.</param>
		internal static void SendEmail(Entry entry, SiteConfig siteConfig, ILoggingDataService logService)
		{
			if (siteConfig.SendPostsByEmail &&
				siteConfig.SmtpServer != null &&
				siteConfig.SmtpServer.Length > 0)
			{
				ArrayList actions = new ArrayList();
				actions.Add(ComposeMail(entry, siteConfig));

				foreach(User user in SiteSecurity.GetSecurity().Users)
				{
					if (user.EmailAddress == null || user.EmailAddress.Length == 0)
						continue;

					if (user.NotifyOnNewPost)
					{
						SendMailInfo sendMailInfo = ComposeMail(entry, siteConfig);
						sendMailInfo.Message.To = user.EmailAddress;
						actions.Add(sendMailInfo);
					}
				}

				IBlogDataService dataService = BlogDataServiceFactory.GetService(HttpContext.Current.Server.MapPath(siteConfig.ContentDir), logService);
				dataService.RunActions((object[])actions.ToArray(typeof(object)));
			}
		}

		private static SendMailInfo ComposeMail(Entry entry, SiteConfig siteConfig)
		{
				MailMessage emailMessage = new MailMessage();

				if (siteConfig.NotificationEMailAddress != null &&
					siteConfig.NotificationEMailAddress.Length > 0)
				{
					emailMessage.To = siteConfig.NotificationEMailAddress;
				}
				else
				{
					emailMessage.To = siteConfig.Contact;
				}

				emailMessage.Subject = String.Format("Weblog post by '{0}' on '{1}'", entry.Author, entry.Title);
				emailMessage.Body = String.Format("{0}<p>Post page: <a href=\"{1}\">{1}</a></p>", entry.Content, Utils.GetPermaLinkUrl(siteConfig,entry));
				emailMessage.BodyFormat = MailFormat.Html;
				emailMessage.BodyEncoding = Encoding.UTF8;

				User entryAuthor = SiteSecurity.GetUser(entry.Author);

				//didn't work? try again with emailAddress...
				if (entryAuthor == null)
				{
					entryAuthor = SiteSecurity.GetUserByEmail(entry.Author);
				}

				if (entryAuthor != null && entryAuthor.EmailAddress != null && entryAuthor.EmailAddress.Length > 0)
				{
					emailMessage.From = entryAuthor.EmailAddress;
				}
				else
				{
					emailMessage.From = siteConfig.Contact;
				}

				SendMailInfo sendMailInfo = new SendMailInfo(emailMessage, siteConfig.SmtpServer,
                    siteConfig.EnableSmtpAuthentication, siteConfig.UseSSLForSMTP, siteConfig.SmtpUserName, siteConfig.SmtpPassword, siteConfig.SmtpPort);

			// Christoph De Baene: Put in comments, because mail is sent through IBlogDataService where the EnableSmtpAuthentication is handled there.
			// RyanG: Added support for authentication on outgoing smtp
			/*
			if (sendMailInfo.EnableSmtpAuthentication)
			{
				sendMailInfo.Message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", "1");	// enable basic authentication
				sendMailInfo.Message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername", sendMailInfo.SmtpUserName);	// set the username
				sendMailInfo.Message.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword", sendMailInfo.SmtpPassword);  // set the password
			}
			*/

			return sendMailInfo;
		}
	}
}
